#include "esp_camera.h"
#include <WiFi.h>
#include <ESP32Servo.h>
#include <HTTPClient.h>
#include <driver/i2s.h>
#include <SD_MMC.h>

// ===========================
// Camera server setup (simplified)
// ===========================
#include "esp_http_server.h"

#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 15
#define SIOD_GPIO_NUM 4
#define SIOC_GPIO_NUM 5

#define Y2_GPIO_NUM 11
#define Y3_GPIO_NUM 9
#define Y4_GPIO_NUM 8
#define Y5_GPIO_NUM 10
#define Y6_GPIO_NUM 12
#define Y7_GPIO_NUM 18
#define Y8_GPIO_NUM 17
#define Y9_GPIO_NUM 16

#define VSYNC_GPIO_NUM 6
#define HREF_GPIO_NUM 7
#define PCLK_GPIO_NUM 13


// ===========================
// Motor control pins (双电机)
// ===========================
#define MOTOR_IN1 36
#define MOTOR_IN2 37
#define ENA 35

#define MOTOR_IN11 39
#define MOTOR_IN22 40
#define ENAA 38


const int SPEED = 130;  //速度控制（0-255）

// ===========================
// WiFi credentials
// ===========================
const char *ssid = "ChinaNet-6x8c";
const char *password = "8zeymm8c";


// ===========================
// MAX98357配置
// ===========================
// 网络WAV音频地址（必须是16000Hz单声道16位PCM编码）
//const char *audioUrl = "http://example.com/11.wav";  // 替换为实际音频URL
// 定义WAV文件路径（TF卡根目录下的test.wav）
const char *WAV_PATH = "/123.wav";

// I2S硬件引脚（与MAX98357连接）
#define I2S_PORT I2S_NUM_0  // 使用I2S端口0
#define I2S_BCLK_PIN 47     // 时钟线（BCLK）
#define I2S_LRCLK_PIN 48    // 左右声道时钟（LRC）x
#define I2S_DATA_PIN 21     // 数据线（DIN）


// ===========================
// Function declarations
// ===========================
void startCameraServer();
void motorLeft();
void motorRight();
void motorForward();
void motorBackward();
void motorStop();
void setupMotor();
esp_err_t initI2S();
void playNetworkWav(const char *url);
void play_local_wav(const char *path);
void skip_wav_header(File &file);


#define PART_BOUNDARY "123456789000000000000987654321"
static const char *_STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char *_STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char *_STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\nX-Timestamp: %d.%06d\r\n\r\n";

httpd_handle_t stream_httpd = NULL;
httpd_handle_t camera_httpd = NULL;

void setup() {
  Serial.begin(115200);

  Serial.setDebugOutput(false);  // Disable debug output to save memory
  Serial.println();
  Serial.println("Starting ESP32-CAM...");

  pinMode(16, INPUT_PULLUP);
  pinMode(15, INPUT_PULLUP);



  // 初始化电机控制
  setupMotor();

  // Camera configuration
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;

  // Reduced configuration to prevent memory overflow
  config.xclk_freq_hz = 20000000;  // 10MHz clock (reduced from 20MHz)
  // config.frame_size = FRAMESIZE_VGA; // VGA resolution (640x480)
  config.frame_size = FRAMESIZE_SVGA;
  config.pixel_format = PIXFORMAT_JPEG;
  config.jpeg_quality = 15;  // Medium quality
  config.fb_count = 1;       // Single frame buffer
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;



  // Adjust based on PSRAM availability
  if (psramFound()) {
    Serial.println("PSRAM detected - using optimized settings");
    config.fb_location = CAMERA_FB_IN_PSRAM;
    config.jpeg_quality = 23;
    config.fb_count = 1;
  } else {
    Serial.println("No PSRAM - using minimal settings");
    config.fb_location = CAMERA_FB_IN_DRAM;
    config.frame_size = FRAMESIZE_QVGA;  // Further reduce to QVGA
    config.jpeg_quality = 18;
  }

  // Initialize camera
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x\n", err);
    Serial.println("Trying with lowest possible configuration...");

    // Try with absolute minimum configuration
    // config.frame_size = FRAMESIZE_QVGA;
    // config.jpeg_quality = 20;
    // config.xclk_freq_hz = 5000000;
    // config.fb_count = 1;

    config.frame_size = FRAMESIZE_QVGA;
    config.jpeg_quality = 30;
    config.xclk_freq_hz = 1500000;
    config.fb_count = 2;

    err = esp_camera_init(&config);

    if (err != ESP_OK) {
      Serial.printf("Camera init failed again with error 0x%x\n", err);
      Serial.println("Restarting in 3 seconds...");
      delay(3000);
      ESP.restart();
      return;
    }
  }

  Serial.println("Camera initialized successfully");

  // Adjust camera settings for better performance
  sensor_t *s = esp_camera_sensor_get();
  if (s->id.PID == OV3660_PID) {
    // Common settings for OV2640
    s->set_vflip(s, 1);        // Vertical flip
    s->set_brightness(s, 1);   // Increase brightness
    s->set_saturation(s, -2);  // Reduce saturation

    // Set to lower resolution for streaming
    s->set_framesize(s, FRAMESIZE_QVGA);
  }

  // Connect to WiFi
  WiFi.begin(ssid, password);
  WiFi.setSleep(false);

  Serial.print("Connecting to WiFi");
  int attempts = 0;
  while (WiFi.status() != WL_CONNECTED && attempts < 25) {
    delay(500);
    Serial.print(".");
    attempts++;
  }

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("\nWiFi connection failed!");
    Serial.println("Please check your credentials and try again.");
    delay(3000);
    ESP.restart();
    return;
  }

  Serial.println("\nWiFi connected successfully!");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // Start camera server
  startCameraServer();
  Serial.println("Camera server started");
  Serial.println("Open http://" + WiFi.localIP().toString() + " in your browser");

  // 初始化TF卡（ESP32-S3-CAM板载TF卡，挂载点/sdcard）
  if (!SD_MMC.begin("/sdcard", true)) {  // true表示强制初始化
    Serial.println("TF卡初始化失败！请检查：1.卡是否插紧 2.格式是否为FAT32");
    while (1)
      ;  // TF卡初始化失败则卡死，避免后续错误
  }
  Serial.println("TF卡初始化成功");

    // 新增：验证WAV文件是否存在
  if (!SD_MMC.exists(WAV_PATH)) {
    Serial.printf("错误：未找到文件 %s\n", WAV_PATH);
    Serial.println("请检查文件路径和文件名");
    while (1);
  }
  Serial.printf("找到文件：%s\n", WAV_PATH);

  // 初始化I2S（适配16000Hz单声道）
  if (initI2S() != ESP_OK) {
    Serial.println("I2S初始化失败！请检查引脚连接");
    while (1) delay(1000);  // 初始化失败则卡死
  }
  Serial.println("I2S初始化完成");

  // 开始播放网络WAV音频
  // Serial.printf("开始播放音频: %s\n", audioUrl);
  //playNetworkWav(audioUrl);
  play_local_wav(WAV_PATH);
  Serial.println("音频播放结束");
}

void loop() {
  // Monitor system health
  static unsigned long lastCheck = millis();
  if (millis() - lastCheck > 30000) {
    lastCheck = millis();

    // Check free memory
    uint32_t freeHeap = ESP.getFreeHeap();
    Serial.printf("Free memory: %d bytes\n", freeHeap);

    // Check WiFi connection
    if (WiFi.status() != WL_CONNECTED) {
      Serial.println("WiFi disconnected! Attempting to reconnect...");
      WiFi.reconnect();
    }

    // Restart if memory gets too low
    if (freeHeap < 15000) {
      Serial.println("Low memory detected, restarting...");
      delay(1000);
      ESP.restart();
    }
  }

  delay(10000);
}

// 初始化电机控制引脚
void setupMotor() {
  pinMode(MOTOR_IN1, OUTPUT);
  pinMode(MOTOR_IN2, OUTPUT);
  analogWrite(ENA, SPEED);  // 速度控制（0-255）

  pinMode(MOTOR_IN11, OUTPUT);
  pinMode(MOTOR_IN22, OUTPUT);
  analogWrite(ENAA, SPEED);  // 速度控制（0-255）
  motorStop();
  Serial.println("Motor pins initialized");
}
// 电机控制函数
void motorForward() {
  digitalWrite(MOTOR_IN1, LOW);
  digitalWrite(MOTOR_IN2, HIGH);
  analogWrite(ENA, SPEED);

  digitalWrite(MOTOR_IN11, HIGH);
  digitalWrite(MOTOR_IN22, LOW);
  analogWrite(ENAA, SPEED);
  Serial.println("Motor: Forward");
}

void motorLeft() {
  digitalWrite(MOTOR_IN11, HIGH);
  digitalWrite(MOTOR_IN22, LOW);
  analogWrite(ENAA, SPEED);

  digitalWrite(MOTOR_IN1, LOW);
  digitalWrite(MOTOR_IN2, LOW);
  analogWrite(ENA, 0);
  Serial.println("Motor: Left");
}

void motorRight() {
  digitalWrite(MOTOR_IN1, LOW);
  digitalWrite(MOTOR_IN2, HIGH);
  analogWrite(ENA, SPEED);

  digitalWrite(MOTOR_IN11, LOW);
  digitalWrite(MOTOR_IN22, LOW);
  analogWrite(ENAA, 0);
  Serial.println("Motor: Right");
}

void motorBackward() {
  digitalWrite(MOTOR_IN1, HIGH);
  digitalWrite(MOTOR_IN2, LOW);
  analogWrite(ENA, SPEED);

  digitalWrite(MOTOR_IN11, LOW);
  digitalWrite(MOTOR_IN22, HIGH);
  analogWrite(ENAA, SPEED);
  Serial.println("Motor: Backward");
}

void motorStop() {
  digitalWrite(MOTOR_IN1, LOW);
  digitalWrite(MOTOR_IN2, LOW);
  analogWrite(ENA, 0);

  digitalWrite(MOTOR_IN11, LOW);
  digitalWrite(MOTOR_IN22, LOW);
  analogWrite(ENAA, 0);
  Serial.println("Motor: Stop");
}

// 电机控制处理函数
static esp_err_t control_handler(httpd_req_t *req) {
  char buf[100];
  int ret, remaining = req->content_len;

  // 读取请求数据
  int total_received = 0;
  while (remaining > 0) {
    ret = httpd_req_recv(req, buf + total_received, min(remaining, (int)sizeof(buf) - total_received - 1));
    if (ret <= 0) {
      return ESP_FAIL;
    }
    total_received += ret;
    remaining -= ret;
  }
  buf[total_received] = '\0';

  // 解析控制命令
  String command = String(buf);
  Serial.println("Received command: " + command);

  if (command == "forward") {
    motorForward();
  } else if (command == "left") {
    motorLeft();
  } else if (command == "right") {
    motorRight();
  } else if (command == "backward") {
    motorBackward();
  } else if (command == "stop") {
    motorStop();
  }

  httpd_resp_set_type(req, "text/plain");
  return httpd_resp_send(req, "OK", 2);
}

// ===================== HTTP服务器处理函数 =====================
static esp_err_t stream_handler(httpd_req_t *req) {
  camera_fb_t *fb = NULL;
  struct timeval _timestamp;
  esp_err_t res = ESP_OK;
  size_t _jpg_buf_len = 0;
  uint8_t *_jpg_buf = NULL;
  char *part_buf[128];

  static int64_t last_frame = 0;
  if (!last_frame) {
    last_frame = esp_timer_get_time();
  }

  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  if (res != ESP_OK) {
    return res;
  }

  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  httpd_resp_set_hdr(req, "X-Framerate", "60");

  while (true) {
    fb = esp_camera_fb_get();
    if (!fb) {
      log_e("Camera capture failed");
      res = ESP_FAIL;
    } else {
      _timestamp.tv_sec = fb->timestamp.tv_sec;
      _timestamp.tv_usec = fb->timestamp.tv_usec;
      if (fb->format != PIXFORMAT_JPEG) {
        bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
        esp_camera_fb_return(fb);
        fb = NULL;
        if (!jpeg_converted) {
          log_e("JPEG compression failed");
          res = ESP_FAIL;
        }
      } else {
        _jpg_buf_len = fb->len;
        _jpg_buf = fb->buf;
      }
    }
    if (res == ESP_OK) {
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
    }
    if (res == ESP_OK) {
      size_t hlen = snprintf((char *)part_buf, 128, _STREAM_PART, _jpg_buf_len, _timestamp.tv_sec, _timestamp.tv_usec);
      res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
    }
    if (res == ESP_OK) {
      res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
    }
    if (fb) {
      esp_camera_fb_return(fb);
      fb = NULL;
      _jpg_buf = NULL;
    } else if (_jpg_buf) {
      free(_jpg_buf);
      _jpg_buf = NULL;
    }
    if (res != ESP_OK) {
      log_e("Send frame failed");
      break;
    }
    int64_t fr_end = esp_timer_get_time();

    int64_t frame_time = fr_end - last_frame;
    last_frame = fr_end;

    frame_time /= 1000;
    log_i(
      "MJPG: %uB %ums (%.1ffps), AVG: %ums (%.1ffps)", (uint32_t)(_jpg_buf_len), (uint32_t)frame_time, 1000.0 / (uint32_t)frame_time, avg_frame_time,
      1000.0 / avg_frame_time);
  }
  return res;
}


static esp_err_t index_handler(httpd_req_t *req) {
  const char *html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
    <title>ESP32-CAM 单电机控制</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" charset="UTF-8">
    <style>
        body { 
            margin: 0; 
            padding: 20px; 
            background: #2c3e50; 
            color: white; 
            font-family: Arial, sans-serif; 
        }
        .container { 
            max-width: 1200px; 
            margin: 0 auto; 
            display: flex;
            flex-wrap: wrap;
            gap: 20px;
            justify-content: center;
        }
        .control-panel { 
            background: #34495e; 
            padding: 20px; 
            border-radius: 10px; 
            flex: 1;
            min-width: 300px;
            max-width: 500px;
        }
        .video-container { 
            background: #34495e; 
            padding: 15px; 
            border-radius: 10px; 
            flex: 1;
            min-width: 300px;
            max-width: 500px;
            display: flex;
            flex-direction: column;
            align-items: center;
            position: relative;
            overflow: visible; /* 确保旋转内容不会溢出 */
        }
        .video-wrapper {
            position: relative;
            width: 100%;
            max-width: 320px;
            margin: 20px 0;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 240px; /* 确保有足够空间容纳旋转后的视频 */
            transition: all 0.3s ease; /* 添加过渡效果 */
        }
        .video-wrapper.original-size {
            max-width: 800px;
            min-height: 600px;
        }
        .btn { 
            padding: 15px 25px; 
            margin: 10px; 
            border: none; 
            border-radius: 8px; 
            font-size: 16px; 
            cursor: pointer; 
        }
        .forward { background: #27ae60; color: white; }
        .backward { background: #e74c3c; color: white; }
        .stop { background: #7f8c8d; color: white; }
        .left { background: #f39c12; color: white; }
        .right { background: #3498db; color: white; }
        .rotate { background: #9b59b6; color: white; }
        .stream-toggle { background: #1abc9c; color: white; }
        .reset-rotate { background: #e67e22; color: white; }
        .original-size-btn { background: #8e44ad; color: white; }
        .save-image-btn { background: #16a085; color: white; }
        .video-stream { 
            width: 100%; 
            max-width: 320px; 
            border: 2px solid #7f8c8d; 
            border-radius: 5px; 
            transition: all 0.3s ease;
            display: block;
        }
        .video-stream.original-size {
            max-width: 800px;
            width: 800px;
            height: 600px;
        }
        .status { 
            margin-top: 10px; 
            color: #bdc3c7; 
            text-align: center;
        }
        
        /* 环状控制球样式 */
        .joystick-container {
            display: flex;
            justify-content: center;
            margin: 20px 0;
        }
        .joystick {
            position: relative;
            width: 200px;
            height: 200px;
            background: #34495e;
            border-radius: 50%;
            border: 3px solid #7f8c8d;
            box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
        }
        .joystick-center {
            position: absolute;
            width: 60px;
            height: 60px;
            background: #e74c3c;
            border-radius: 50%;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            transition: transform 0.1s ease;
            box-shadow: 0 0 10px rgba(0,0,0,0.3);
        }
        .joystick.active .joystick-center {
            background: #c0392b;
        }
        
        /* 按钮布局优化 */
        .button-row {
            display: flex;
            justify-content: center;
            flex-wrap: wrap;
        }
        .button-row .btn {
            flex: 1;
            min-width: 120px;
            max-width: 150px;
        }
        .direction-controls {
            display: flex;
            flex-direction: column;
            align-items: center;
            margin-bottom: 15px;
        }
        
        /* 视频控制按钮样式 */
        .video-controls {
            display: flex;
            justify-content: center;
            margin-top: 20px;
            flex-wrap: wrap;
            position: relative;
            z-index: 10; /* 确保按钮在旋转内容之上 */
            transition: margin-top 0.3s ease; /* 添加过渡效果 */
        }
        .video-controls.original-size {
            margin-top: 40px; /* 当视频变大时，按钮下移 */
        }
        .video-controls .btn {
            padding: 10px 20px;
            font-size: 14px;
            margin: 5px;
        }
        
        /* 响应式设计 */
        @media (max-width: 768px) {
            .container {
                flex-direction: column;
            }
            .control-panel, .video-container {
                max-width: 100%;
            }
            .video-wrapper {
                min-height: 200px;
            }
            .video-wrapper.original-size {
                max-width: 100%;
                min-height: 400px;
            }
            .video-stream.original-size {
                max-width: 100%;
                width: 100%;
                height: auto;
            }
        }
        
        h2 {
            text-align: center;
            margin-top: 0;
            position: relative;
            z-index: 10; /* 确保标题在旋转内容之上 */
        }
        
        .video-status-area {
            position: relative;
            z-index: 10; /* 确保状态信息在旋转内容之上 */
            margin-top: 10px;
        }

        /* 保存成功提示 */
        .save-notification {
            position: fixed;
            top: 20px;
            right: 20px;
            background: #27ae60;
            color: white;
            padding: 15px 20px;
            border-radius: 5px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            z-index: 1000;
            opacity: 0;
            transform: translateX(100px);
            transition: all 0.3s ease;
        }
        
        .save-notification.show {
            opacity: 1;
            transform: translateX(0);
        }
    </style>
</head>
<body>
    <!-- 保存成功提示 -->
    <div id="saveNotification" class="save-notification">图片保存成功！</div>

    <div class="container">
        <!-- 左侧：控制面板 -->
        <div class="control-panel">
            <h2>电机控制面板</h2>
            
            <!-- 方向控制按钮 -->
            <div class="direction-controls">
                <div>
                    <button class="btn forward" onmousedown="controlMotor('forward')" ontouchstart="controlMotor('forward')" onmouseup="controlMotor('stop')" ontouchend="controlMotor('stop')">前进</button>
                </div>
                <div class="button-row">
                    <button class="btn left" onmousedown="controlMotor('left')" ontouchstart="controlMotor('left')" onmouseup="controlMotor('stop')" ontouchend="controlMotor('stop')">左转</button>
                    <button class="btn stop" onclick="controlMotor('stop')">停止</button>
                    <button class="btn right" onmousedown="controlMotor('right')" ontouchstart="controlMotor('right')" onmouseup="controlMotor('stop')" ontouchend="controlMotor('stop')">右转</button>
                </div>
                <div>
                    <button class="btn backward" onmousedown="controlMotor('backward')" ontouchstart="controlMotor('backward')" onmouseup="controlMotor('stop')" ontouchend="controlMotor('stop')">后退</button>
                </div>
            </div>
            
            <!-- 环状控制球 -->
            <div class="joystick-container">
                <div class="joystick" id="joystick">
                    <div class="joystick-center"></div>
                </div>
            </div>
            
            <div id="status" class="status">就绪</div>
        </div>

        <!-- 右侧：视频面板 -->
        <div class="video-container">
            <h2>视频监控</h2>
            <div class="video-wrapper" id="videoWrapper">
                <img src="http://192.168.1.49:81/stream" class="video-stream" id="videoStream"
                     onload="document.getElementById('videoStatus').textContent='视频流连接正常'"
                     onerror="document.getElementById('videoStatus').textContent='视频流连接失败'">
            </div>
            <div class="video-controls" id="videoControls">
                <!-- 旋转按钮 -->
                <button class="btn rotate" onclick="rotateVideo(10)">顺时针旋转10°</button>
                <button class="btn rotate" onclick="rotateVideo(-10)">逆时针旋转10°</button>
                <!-- 重置旋转按钮 -->
                <button class="btn reset-rotate" onclick="resetRotation()">重置旋转</button>
                <!-- 视频流控制按钮 -->
                <button class="btn stream-toggle" id="streamToggle" onclick="toggleVideoStream()">关闭视频流</button>
                <!-- 显示原图按钮 -->
                <button class="btn original-size-btn" id="originalSizeToggle" onclick="toggleOriginalSize()">显示原图</button>
                <!-- 新增：保存图片按钮 -->
                <button class="btn save-image-btn" id="saveImageBtn" onclick="saveCurrentImage()">保存图片</button>
            </div>
            <div class="video-status-area">
                <div id="videoStatus" class="status">正在连接视频流...</div>
                <div id="rotationStatus" class="status">旋转角度: 0°</div>
            </div>
        </div>
    </div>

    <script>
        // 新增：视频旋转相关变量
        let currentRotation = 0;
        
        // 新增：视频流控制变量
        let isStreamActive = true;
        const videoStream = document.getElementById('videoStream');
        const streamToggle = document.getElementById('streamToggle');
        const originalStreamSrc = videoStream.src;
        
        // 新增：原图显示控制变量
        let isOriginalSize = false;
        const originalSizeToggle = document.getElementById('originalSizeToggle');
        const videoWrapper = document.getElementById('videoWrapper');
        const videoControls = document.getElementById('videoControls');

        // 新增：保存图片相关变量
        const saveImageBtn = document.getElementById('saveImageBtn');
        const saveNotification = document.getElementById('saveNotification');
        
        // 修改：旋转视频函数，支持任意角度
        function rotateVideo(angle) {
            currentRotation = (currentRotation + angle) % 360;
            // 确保角度在0-359之间
            if (currentRotation < 0) {
                currentRotation += 360;
            }
            videoStream.style.transform = `rotate(${currentRotation}deg)`;
            updateRotationStatus();
        }

        // 新增：重置旋转函数
        function resetRotation() {
            currentRotation = 0;
            videoStream.style.transform = `rotate(${currentRotation}deg)`;
            updateRotationStatus();
        }

        // 新增：更新旋转状态显示
        function updateRotationStatus() {
            document.getElementById('rotationStatus').textContent = `旋转角度: ${currentRotation}°`;
        }

        // 新增：切换视频流函数
        function toggleVideoStream() {
            if (isStreamActive) {
                // 关闭视频流
                videoStream.src = "";
                streamToggle.textContent = "开启视频流";
                document.getElementById('videoStatus').textContent = "视频流已关闭";
                isStreamActive = false;
            } else {
                // 开启视频流
                videoStream.src = originalStreamSrc + '?t=' + Date.now();
                streamToggle.textContent = "关闭视频流";
                document.getElementById('videoStatus').textContent = "正在连接视频流...";
                isStreamActive = true;
            }
        }

        // 新增：切换原图显示函数
        function toggleOriginalSize() {
            if (isOriginalSize) {
                // 恢复默认大小
                videoWrapper.classList.remove('original-size');
                videoStream.classList.remove('original-size');
                videoControls.classList.remove('original-size');
                originalSizeToggle.textContent = "显示原图";
                isOriginalSize = false;
            } else {
                // 显示原图大小
                videoWrapper.classList.add('original-size');
                videoStream.classList.add('original-size');
                videoControls.classList.add('original-size');
                originalSizeToggle.textContent = "恢复默认";
                isOriginalSize = true;
            }
        }

        // 新增：保存当前图片函数
        function saveCurrentImage() {
            if (!isStreamActive) {
                alert('请先开启视频流！');
                return;
            }

            // 创建Canvas来绘制图像（包括旋转效果）
            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');
            
            // 设置Canvas尺寸为视频的实际尺寸
            const videoWidth = videoStream.videoWidth || videoStream.naturalWidth;
            const videoHeight = videoStream.videoHeight || videoStream.naturalHeight;
            
            if (videoWidth === 0 || videoHeight === 0) {
                alert('无法获取视频尺寸，请等待视频加载完成！');
                return;
            }

            canvas.width = videoWidth;
            canvas.height = videoHeight;

            // 保存Canvas状态
            ctx.save();
            
            // 如果视频有旋转，应用相同的旋转到Canvas
            if (currentRotation !== 0) {
                // 移动到中心点
                ctx.translate(canvas.width / 2, canvas.height / 2);
                // 应用旋转
                ctx.rotate(currentRotation * Math.PI / 180);
                // 绘制图像（调整位置）
                ctx.drawImage(videoStream, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height);
            } else {
                // 无旋转，直接绘制
                ctx.drawImage(videoStream, 0, 0, canvas.width, canvas.height);
            }
            
            // 恢复Canvas状态
            ctx.restore();

            // 将Canvas转换为Data URL
            canvas.toBlob(function(blob) {
                // 创建下载链接
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
                const filename = `esp32-cam-snapshot_${timestamp}_rot${currentRotation}.png`;
                
                a.href = url;
                a.download = filename;
                a.style.display = 'none';
                
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                
                // 释放URL对象
                URL.revokeObjectURL(url);
                
                // 显示保存成功提示
                showSaveNotification();
                
                console.log(`图片已保存: ${filename}`);
            }, 'image/png', 0.95);
        }

        // 新增：显示保存成功提示
        function showSaveNotification() {
            saveNotification.classList.add('show');
            setTimeout(() => {
                saveNotification.classList.remove('show');
            }, 3000);
        }

        // 新增：键盘快捷键（S键保存图片）
        document.addEventListener('keydown', function(event) {
            if (event.code === 'KeyS' && !event.ctrlKey && !event.altKey && !event.metaKey) {
                event.preventDefault();
                saveCurrentImage();
            }
        });

        function controlMotor(direction) {
            const status = document.getElementById('status');
            status.textContent = '控制: ' + direction;
            
            fetch('/control', {
                method: 'POST',
                body: direction,
                headers: {
                    'Content-Type': 'text/plain'
                }
            })
            .then(response => response.text())
            .then(data => {
                console.log('控制成功:', direction);
            })
            .catch(error => {
                status.textContent = '控制失败';
                console.error('错误:', error);
            });
        }

        // 环状控制球逻辑
        const joystick = document.getElementById('joystick');
        const joystickCenter = joystick.querySelector('.joystick-center');
        let isDragging = false;
        let lastDirection = '';

        // 鼠标事件
        joystick.addEventListener('mousedown', startDrag);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', stopDrag);
        
        // 触摸事件
        joystick.addEventListener('touchstart', function(e) {
            e.preventDefault();
            startDrag(e.touches[0]);
        });
        document.addEventListener('touchmove', function(e) {
            e.preventDefault();
            drag(e.touches[0]);
        });
        document.addEventListener('touchend', stopDrag);
        
        function startDrag(e) {
            isDragging = true;
            joystick.classList.add('active');
            updateJoystick(e);
        }
        
        function drag(e) {
            if (!isDragging) return;
            updateJoystick(e);
        }
        
        function stopDrag() {
            if (!isDragging) return;
            isDragging = false;
            joystick.classList.remove('active');
            joystickCenter.style.transform = 'translate(-50%, -50%)';
            
            // 停止电机
            if (lastDirection) {
                controlMotor('stop');
                lastDirection = '';
            }
        }
        
        function updateJoystick(e) {
            const rect = joystick.getBoundingClientRect();
            const centerX = rect.left + rect.width / 2;
            const centerY = rect.top + rect.height / 2;
            const deltaX = e.clientX - centerX;
            const deltaY = e.clientY - centerY;
            
            // 计算距离和角度
            const distance = Math.min(Math.sqrt(deltaX * deltaX + deltaY * deltaY), rect.width / 2);
            const angle = Math.atan2(deltaY, deltaX);
            
            // 更新控制球位置
            const maxDistance = rect.width / 2 - joystickCenter.offsetWidth / 2;
            const limitedDistance = Math.min(distance, maxDistance);
            const posX = limitedDistance * Math.cos(angle);
            const posY = limitedDistance * Math.sin(angle);
            
            joystickCenter.style.transform = `translate(${posX}px, ${posY}px)`;
            
            // 确定方向
            let direction = '';
            const angleDeg = angle * 180 / Math.PI;
            
            if (distance > 30) { // 最小有效距离
                if (angleDeg >= -45 && angleDeg < 45) {
                    direction = 'right';
                } else if (angleDeg >= 45 && angleDeg < 135) {
                    direction = 'backward';
                } else if (angleDeg >= -135 && angleDeg < -45) {
                    direction = 'forward';
                } else {
                    direction = 'left';
                }
                
                // 只有当方向改变时才发送命令
                if (direction !== lastDirection) {
                    controlMotor(direction);
                    lastDirection = direction;
                }
            }
        }

        // 自动重连视频流（仅在视频流开启时）
        setInterval(() => {
            if (isStreamActive) {
                const img = document.querySelector('.video-stream');
                if (img && img.complete && img.naturalHeight === 0) {
                    img.src = img.src.split('?')[0] + '?t=' + Date.now();
                }
            }
        }, 5000);

        // 更新标题显示快捷键信息
        document.addEventListener('DOMContentLoaded', function() {
            const title = document.querySelector('title');
            title.textContent = 'ESP32-CAM 单电机控制 - 按S键保存图片';
            
            const stageTitle = document.querySelector('h2');
            if (stageTitle && stageTitle.textContent.includes('视频监控')) {
                stageTitle.textContent = '视频监控 - 按S键保存图片';
            }
        });
    </script>
</body>
</html>
)rawliteral";

  httpd_resp_set_type(req, "text/html");
  return httpd_resp_send(req, html, strlen(html));
}

void startCameraServer() {
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
  // config.stack_size = 4096;  // 堆栈大小
  config.max_uri_handlers = 16;
  // config.lru_purge_enable = true;

  httpd_uri_t index_uri = {
    .uri = "/",
    .method = HTTP_GET,
    .handler = index_handler,
    .user_ctx = NULL
  };

  httpd_uri_t stream_uri = {
    .uri = "/stream",
    .method = HTTP_GET,
    .handler = stream_handler,
    .user_ctx = NULL
  };

  httpd_uri_t control_uri = {
    .uri = "/control",
    .method = HTTP_POST,
    .handler = control_handler,
    .user_ctx = NULL
  };
  if (httpd_start(&camera_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(camera_httpd, &index_uri);
    httpd_register_uri_handler(camera_httpd, &control_uri);
    Serial.println("camera_httpd服务器启动成功");
  } else {
    Serial.println("camera_httpd服务器启动失败");
  }

  config.server_port += 1;
  config.ctrl_port += 1;
  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &stream_uri);
    Serial.println("stream_httpd服务器启动成功");
  } else {
    Serial.println("stream_httpd服务器启动失败");
  }
}

// 初始化I2S配置（适配16000Hz单声道16位PCM）
esp_err_t initI2S() {
  // 配置I2S参数
  i2s_config_t i2sConfig = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),  // 关键：强制转换为 i2s_mode_t 类型
    .sample_rate = 16000,                                 // 采样率：16000Hz
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,         // 16位位深
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,          // 单声道（左声道）
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,    // 标准I2S格式
    .dma_buf_count = 8,                                   // DMA缓冲区数量
    .dma_buf_len = 512,                                   // 每个缓冲区大小（字节）
    .use_apll = false,                                    // 不使用APLL时钟
    .tx_desc_auto_clear = true,                           // 自动清理发送描述符
    .fixed_mclk = 0                                       // 不固定MCLK时钟
  };

  // 安装I2S驱动
  esp_err_t err = i2s_driver_install(I2S_PORT, &i2sConfig, 0, NULL);
  if (err != ESP_OK) return err;

  // 配置I2S引脚映射
  i2s_pin_config_t pinConfig = {
    .bck_io_num = I2S_BCLK_PIN,       // 时钟线引脚
    .ws_io_num = I2S_LRCLK_PIN,       // 左右声道时钟引脚
    .data_out_num = I2S_DATA_PIN,     // 数据线引脚
    .data_in_num = I2S_PIN_NO_CHANGE  // 不使用输入
  };

  // 设置I2S引脚
  err = i2s_set_pin(I2S_PORT, &pinConfig);
  if (err != ESP_OK) {
    i2s_driver_uninstall(I2S_PORT);  // 引脚配置失败则卸载驱动
    return err;
  }

  // 启动I2S发送
  i2s_start(I2S_PORT);
  return ESP_OK;
}

// 从网络获取WAV文件并通过I2S播放
void playNetworkWav(const char *url) {
  HTTPClient http;

  // 启动HTTP连接
  if (!http.begin(url)) {
    Serial.println("HTTP初始化失败");
    return;
  }

  // 发送HTTP GET请求
  int httpCode = http.GET();
  if (httpCode != HTTP_CODE_OK) {
    Serial.printf("HTTP请求失败，错误码: %d\n", httpCode);
    http.end();
    return;
  }

  // 获取音频数据流
  WiFiClient *stream = http.getStreamPtr();
  if (!stream) {
    Serial.println("无法获取音频流");
    http.end();
    return;
  }

  // 跳过WAV文件头部（标准PCM WAV头部为44字节）
  uint8_t wavHeader[44];
  size_t headerRead = stream->readBytes(wavHeader, 44);
  if (headerRead != 44) {
    Serial.println("WAV文件头部不完整，可能不是标准PCM文件");
    http.end();
    return;
  }
  Serial.println("已跳过WAV头部，开始播放音频数据");

  // 读取音频数据并通过I2S输出
  const size_t bufferSize = 1024;  // 数据缓冲区大小（需为偶数，16位数据对齐）
  uint8_t audioBuffer[bufferSize];
  size_t totalBytesPlayed = 0;

  while (stream->available()) {
    // 从网络读取数据到缓冲区
    size_t bytesRead = stream->readBytes(audioBuffer, bufferSize);
    if (bytesRead == 0) break;

    // 通过I2S输出音频数据
    size_t bytesWritten = 0;
    esp_err_t err = i2s_write(I2S_PORT, audioBuffer, bytesRead, &bytesWritten, portMAX_DELAY);
    if (err != ESP_OK) {
      Serial.printf("I2S写入失败，错误码: %d\n", err);
      break;
    }

    totalBytesPlayed += bytesWritten;
    // 每播放4KB数据打印一次进度（可选）
    if (totalBytesPlayed % 4096 == 0) {
      Serial.printf("已播放: %.2f KB\n", totalBytesPlayed / 1024.0);
    }
  }

  Serial.printf("播放完成，总数据量: %.2f KB\n", totalBytesPlayed / 1024.0);
  http.end();  // 关闭HTTP连接
}

// 播放WAV文件
void play_local_wav(const char *path) {
  File wavFile = SD_MMC.open(path);
  if (!wavFile) {
    Serial.printf("无法打开文件：%s\n", path);
    return;
  }
  Serial.printf("开始播放：%s（大小：%d字节）\n", path, wavFile.size());

  // 跳过WAV头
  skip_wav_header(wavFile);

  // 读取音频数据并通过I2S输出
  const int BUFFER_SIZE = 1024;  // 缓冲区大小（字节）
  uint8_t buffer[BUFFER_SIZE];

  while (wavFile.available()) {
    size_t bytesRead = wavFile.read(buffer, BUFFER_SIZE);
    if (bytesRead == 0) break;

    // 写入I2S缓冲区（阻塞直到发送完成）
    size_t bytesWritten = 0;
    // i2s_write(I2S_NUM_0, buffer, bytesRead, &bytesWritten, portMAX_DELAY);
    i2s_write(I2S_PORT, buffer, bytesRead, &bytesWritten, portMAX_DELAY);

  }

  // 清理
  wavFile.close();
  Serial.println("播放完成");
}

// 跳过WAV文件头（WAV文件前44字节为格式信息，需跳过）
void skip_wav_header(File &file) {
  if (file.size() < 44) {
    Serial.println("WAV文件过小，不是有效格式");
    return;
  }
  // 读取并跳过前44字节头信息
  uint8_t header[44];
  file.read(header, 44);

  // （可选）验证WAV格式（检查"RIFF"和"WAVE"标识）
  if (header[0] != 'R' || header[1] != 'I' || header[2] != 'F' || header[3] != 'F') {
    Serial.println("不是有效的RIFF格式文件");
  }
  if (header[8] != 'W' || header[9] != 'A' || header[10] != 'V' || header[11] != 'E') {
    Serial.println("不是有效的WAV格式文件");
  }
}
